{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "208706e7-4516-43a1-844b-7da2191a281e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 文心一言+\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5dacd508-f6ee-4d30-a287-358a44dad838",
   "metadata": {},
   "source": [
    "# 棋盘信息"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f822e64-d453-4374-ac61-2b74707076be",
   "metadata": {
    "tags": []
   },
   "source": [
    "## 棋盘信息设置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f7f15e0b-c074-452e-abd8-8eade89e0574",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "board_info = {\n",
    "    \"shape\":[19,19,3],\n",
    "    \"empty\":[0.95,0.95,0.95],\n",
    "    \"B\":[0.,0.,0.],\n",
    "    \"W\":[0.7,0.7,0.7]\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "604128ca-ce64-41b6-875a-b48b34fe7d1b",
   "metadata": {},
   "source": [
    "## 创建棋盘状态"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "699b3ae9-0236-4cfa-9454-e9d8a0c78726",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "class:  ndarray\n",
      "shape:  (19, 19, 3)\n",
      "strides:  (456, 24, 8)\n",
      "itemsize:  8\n",
      "aligned:  True\n",
      "contiguous:  True\n",
      "fortran:  False\n",
      "data pointer: 0x3088a60\n",
      "byteorder:  little\n",
      "byteswap:  False\n",
      "type: float64\n"
     ]
    }
   ],
   "source": [
    "board_array = np.full((board_info[\"shape\"]),board_info[\"empty\"],dtype=float)\n",
    "np.info(board_array)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59236c32-2eb2-4f52-b721-83071d479bf6",
   "metadata": {},
   "source": [
    "## 按步记录棋谱的棋盘状态\n",
    "\n",
    ">用于 scikit-learn、tensorflow 分析训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a6636620-cd19-4cba-a158-00342d047f8d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "class:  ndarray\n",
      "shape:  (1, 19, 19, 3)\n",
      "strides:  (8664, 456, 24, 8)\n",
      "itemsize:  8\n",
      "aligned:  True\n",
      "contiguous:  True\n",
      "fortran:  False\n",
      "data pointer: 0x308d070\n",
      "byteorder:  little\n",
      "byteswap:  False\n",
      "type: float64\n"
     ]
    }
   ],
   "source": [
    "sgf_array = np.expand_dims(board_array.copy(), axis=0)\n",
    "np.info(sgf_array)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1bc8e5b8-01f5-4469-991e-b0dad62c6bbb",
   "metadata": {},
   "source": [
    "# 读取棋谱"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e6886b1-16cb-4a8f-982a-88c5dd9acffa",
   "metadata": {},
   "source": [
    "## 获取棋谱文件内容"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "9ac5e70d-9123-4e7b-8cd4-3ed06c13efa7",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import glob\n",
    "# 指定文件夹路径\n",
    "folder_path = 'sgf'\n",
    "# 使用glob模块查找所有的.sgf文件\n",
    "sgf_files = glob.glob(os.path.join(folder_path, '*.sgf'))\n",
    "# 遍历所有找到的.sgf文件\n",
    "lines_array = []\n",
    "for sgf_file in sgf_files:\n",
    "    with open(sgf_file, 'r', encoding=\"GBK\") as file:\n",
    "        lines = [line for line in file.readlines() if line.strip() != '']\n",
    "        lines_array.append(lines)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74365e16-0f54-44e0-8677-6117b64c9cec",
   "metadata": {},
   "source": [
    "## 将棋谱内容读取到变量\n",
    "\n",
    "> 通过 RE 判断胜方\n",
    "> 将 ; 开头的行，以 ; 划分成棋步列表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "022fc65d-689a-4833-bcdf-056b608263bd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "胜方(RE)： 白\n",
      "B[qd];W[pp];B[dc];W[cp];B[qq];W[pq];B[qp];W[qn];B[qo];W[po];B[rn];W[ce];B[od];W[ed];B[ec];W[fd];B[gc];W[qr];B[rr];W[ro];B[rp];W[rm];B[ep];W[eq];B[fq];W[dq];B[fp];W[sn];B[pr];W[cn];B[jp];W[pj];B[gd];W[mc];B[qh];W[ob];B[pc];W[jc];B[cg];W[dg];B[dh];W[eg];B[ci];W[cc];B[ic];W[ib];B[dd];W[cd];B[de];W[cf];B[bg];W[df];B[id];W[hb];B[ff];W[jd];B[cm];W[bm];B[bl];W[cl];B[dm];W[bk];B[bn];W[al];B[co];W[bo];B[dn];W[an];B[ej];W[do];B[cn];W[bq];B[dl];W[eh];B[dj];W[ko];B[jo];W[kn];B[kp];W[lp];B[lq];W[mp];B[pn];W[pm];B[on];W[om];B[je];W[ld];B[jn];W[km];B[oq];W[oo];B[nn];W[no];B[mq];W[jm];B[mn];W[mm];B[ql];W[qm];B[nk];W[or];B[qs];W[nq];B[nr];W[op];B[os];W[nj];B[mj];W[ni];B[nm];W[mk];B[nl];W[ln];B[lk];W[ml];B[oq];W[mo];B[np];W[lo];B[lm];W[ll];B[kl];W[lm];B[im];W[jl];B[ik];W[kk];B[il];W[pb];B[qb];W[rc];B[qc];W[qf];B[pf];W[qg];B[pg];W[rh];B[qe];W[qi];B[lj];W[ok];B[cb];W[bb];B[db];W[fg];B[hg];W[gf];B[ee];W[hh];B[gg];W[gh];B[fi];W[ih];B[if];W[bf];B[ba];W[ab];B[er];W[dp];B[eo];W[kj];B[jk];W[ol];B[ck];W[bj];B[bl];W[am];B[bi];W[fb];B[gb];W[ga];B[fc];W[re];B[rd];W[jg];B[dr];W[cr];B[ke];W[mg];B[ji];W[jj];B[kd];W[kc];B[ij];W[ii];B[gi];W[rf];B[ph];W[sd];B[se];W[sf];B[lh];W[lg];B[ki];W[kl];B[fh];W[af];B[kg];W[kf];B[jh];W[kh];B[ri];W[rj];B[kg];W[ig];B[hf];W[kh];B[bd];W[le];B[bc];W[sb];B[ng];W[ne])\n"
     ]
    }
   ],
   "source": [
    "go_records = []\n",
    "f_go = lines_array[np.random.randint(0, len(lines_array), size=1)[0]]\n",
    "for line in f_go:\n",
    "    if line[:2] == \"RE\":\n",
    "        print(\"胜方(RE)：\",line[3])\n",
    "    elif line[0] == \";\":\n",
    "        line = line.strip()\n",
    "        go_records.extend([line for line in line.split(\";\") if line.strip() != ''])\n",
    "print(\";\".join(go_records))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45f1d01f-c659-4cdd-bfc9-84ad6a833a11",
   "metadata": {},
   "source": [
    "## 棋串处理函数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24e3627c-8f5c-4c4a-b2f0-9562f3f4dc03",
   "metadata": {},
   "source": [
    "### 获取棋步上下左右的索引"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "df056a41-0afd-4fe7-8534-b49cac48f130",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def get_neighbor_indices(array, row, col):\n",
    "    # 获取数组的行数和列数\n",
    "    num_rows, num_cols = array.shape[0], array.shape[1]\n",
    "    # 检查坐标是否在数组范围内\n",
    "    if row < 0 or row >= num_rows or col < 0 or col >= num_cols:\n",
    "        return None\n",
    "    # 计算上下左右的索引\n",
    "    up = (max(0, row - 1), col)\n",
    "    down = (min(num_rows-1, row + 1), col)\n",
    "    left = (row, max(0, col - 1))\n",
    "    right = (row, min(num_cols-1, col + 1))\n",
    "    a = set((up, down, left, right))\n",
    "    a.discard((row, col))\n",
    "    return list(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11429153-c8fa-4a19-bacd-6cb7ee0dba88",
   "metadata": {},
   "source": [
    "### 获取棋步所在棋串"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a959fc30-c64a-4c10-ac31-1ed04434f33a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_equal_neighbors(array, row, col):\n",
    "    num_rows, num_cols = array.shape[0], array.shape[0]\n",
    "    a=0\n",
    "    neighbor_indices = []\n",
    "    neighbor_indices.append((row, col))\n",
    "    near_indices = []\n",
    "    space_indices = []\n",
    "    while a < len(neighbor_indices):\n",
    "        b = neighbor_indices[a]\n",
    "        c = get_neighbor_indices(array, b[0], b[1])\n",
    "        for d in c:\n",
    "            if np.all(array[d[0],d[1]] == board_info[\"empty\"]):\n",
    "                space_indices.append(d)\n",
    "            elif np.all(array[d[0],d[1]] == array[row, col]) and d not in neighbor_indices:\n",
    "                neighbor_indices.append(d)\n",
    "            elif np.all(array[d[0],d[1]] != array[row, col]) and d not in near_indices:\n",
    "                near_indices.append(d)\n",
    "        a+=1\n",
    "    return [neighbor_indices,near_indices,space_indices]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f83fc8d0-17bf-499c-8316-4142403c1643",
   "metadata": {},
   "source": [
    "## 棋步转换到棋盘状态"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ccbee948-7dcd-4e34-ac8b-18d99b333cae",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "214 107 107\n",
      "class:  ndarray\n",
      "shape:  (215, 19, 19, 3)\n",
      "strides:  (8664, 456, 24, 8)\n",
      "itemsize:  8\n",
      "aligned:  True\n",
      "contiguous:  True\n",
      "fortran:  False\n",
      "data pointer: 0x7f7c99e95010\n",
      "byteorder:  little\n",
      "byteswap:  False\n",
      "type: float64\n"
     ]
    }
   ],
   "source": [
    "b_count=0\n",
    "w_count=0\n",
    "sgf_array = np.expand_dims(board_array.copy(), axis=0)\n",
    "for pieces in go_records:\n",
    "    board_array[ord(pieces[2])-97,ord(pieces[3])-97] = board_info[pieces[0]]\n",
    "    if pieces[0] == \"B\":\n",
    "        b_count+=1\n",
    "    if pieces[0] == \"W\":\n",
    "        w_count+=1\n",
    "\n",
    "    [neighbor,near,space] = get_equal_neighbors(board_array,ord(pieces[2])-97,ord(pieces[3])-97)\n",
    "\n",
    "    for a in near:\n",
    "        if np.all(board_array[a[0],a[1]] != board_info[\"empty\"]):\n",
    "            [b,c,d] = get_equal_neighbors(board_array,a[0],a[1])\n",
    "            if len(d)==0:\n",
    "                for e in b:\n",
    "                    board_array[e[0],e[1]] = board_info[\"empty\"]\n",
    "                space.append(a)\n",
    "\n",
    "    #复盘棋谱正常是不需要判断棋步是否可以下棋\n",
    "    if len(space)==0:\n",
    "        board_array[ord(pieces[2])-97,ord(pieces[3])-97] = board_info[\"empty\"]\n",
    "\n",
    "    sgf_array = np.append(sgf_array, [board_array], axis=0)\n",
    "\n",
    "print(len(go_records),b_count,w_count)\n",
    "np.info(sgf_array)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f2d3459-a2aa-4fbf-81b9-b348c93b4f6c",
   "metadata": {},
   "source": [
    "# Tensorflow 数据分析"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfdd10ce-3cb4-47cd-891a-f0576818f5f6",
   "metadata": {},
   "source": [
    "## 设置打印警告、错误和致命错误的信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ebe5aa81-5654-4d8a-87e3-d622299bd292",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'\n",
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7af7ef02-ae7f-4d2b-bc83-c23cfaaf824c",
   "metadata": {},
   "source": [
    "## 卷积长短期记忆建模"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c86753d0-966a-4226-bd6e-5a5c3e5c6e72",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 19, 19, 1)\n"
     ]
    }
   ],
   "source": [
    "input_tensor = tf.convert_to_tensor(np.expand_dims(sgf_array, axis=0), dtype=tf.float32)\n",
    "# 创建一个 ConvLSTM3D 层，指定输出通道数为 2，滤波器的大小为 (3, 3, 3)，使用 'same' 填充方式\n",
    "conv_lstm_layer = tf.keras.layers.ConvLSTM2D(\n",
    "    filters=1,\n",
    "    kernel_size=(3, 3),\n",
    "    padding='same'\n",
    ")\n",
    "# 将输入张量传递给 ConvLSTM3D 层\n",
    "output_tensor = conv_lstm_layer(input_tensor)\n",
    "# 打印输出张量的形状\n",
    "print(output_tensor.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c1f2240-7805-4e8f-9fe4-7710d67aa1a6",
   "metadata": {},
   "source": [
    "# 展示棋谱"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "5953c468-9984-4743-9cfc-5be3ff41d528",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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oFWNCgsLRbdu3cTixYvFzp07xbx580S7du3EN998o22AGIYITqYMk2RuueUWcdJJJ4krrrhCDBw4MGg9SS3Mnz9fFBcXB21buXKlUFVV3HvvvaJjx47i3//+d+C1lklp4MCBYtasWZr2tWPHDgFArF27VrO/W2+9VVx66aWB/0+ePFl069ZN1NfXB7YtWbJEdOrUSfh8PiFE87Jy5eXlQTotfV9xxRXi97//fVCbu+++W5x66qmB/xcWFoqrrroq8P+mpibRs2fPiGtfMkyy4Mu8DJNknnjiCfz3v//FqlWr8Pe//x0GgyFq+xUrVmDYsGHIy8tDp06dcP/992PPnj1BbSZMmIBLLrkEjz76KJ544gn0798/ot7tt9+Ohx9+GMOGDcPMmTPxxRdfRGy7bds2ZGVlYfjw4RHbLF68GMXFxejRowc6deqEZ599NsTfoEGDcNxxxwX+P3ToUBw9ehS1tbVR+34sX3/9NYYNGxa0bdiwYdi5cyd8Pl9g2+mnnx74t6IoyMvLw4EDBzTvh2Eo4GTKMElm165d+PHHH9HU1ITvv/8+atvNmzfjyiuvxJgxY7BmzRp89tln+POf/xz4vdHPL7/8gurqamRlZWHnzp1RNa+//np89913uPrqq7F9+3acddZZWLhwYdi2OTk5UbVeffVV3HXXXZg6dSr+9a9/Ydu2bbj22mtD/KWS7OzsoP8rioKmpqY0uWHaKpxMGSaJNDY24qqrrsJll12GOXPm4Prrr4/6rWnTpk0oLCzEn//8Z5x11lno378/du/eHdLuzjvvRLt27fD2229jwYIFeP/996P6MJvNuOmmm2C323HnnXfiueeeC9tu4MCBaGpqwoYNG8K+/uGHH+Kcc87BLbfcgjPPPBMnnngidu3aFdLu888/x6+//hr4/5YtW9CpUyeYzWYAzXfuHvvtMhynnHIKPvzww5D9n3TSScjKyor6XoZJNZxMGSaJ/PnPf8bhw4exYMGCwF261113XcT2/fv3x549e/Dqq69i165dWLBgAVavXh3U5s0338QLL7yAv//97/j973+Pu+++G5MnT8bPP/8cVnP69Ol499134XQ6sXXrVqxbtw6nnHJK2LZFRUWYPHkyrrvuOrz22mtwOp1Yv349Vq5cGfD36aef4t1338W///1vPPDAA/jkk09CdBobGzF16lR89dVXeOuttzBz5kxMmzYN7dq1C+zno48+wvfff4+DBw+G/SZ55513oqqqCnPmzMG///1vLF++HIsWLcJdd90V8fgxTNpI94+2DNNaWbdunWjfvr1wOByBbU6nU3Tp0kU8/fTTEd939913C6PRGLgj9qmnnhK5ublCCCEOHDggevXqJebOnRto39jYKIqLi8XEiRMD+8UxN/JMmzZN9OvXTxgMBtGjRw9x9dVXi4MHD0bc/6+//iruuOMOkZ+fLzp06CBOPPFE8cILLwghhGhoaBBTpkwRubm5omvXruLmm28W9957rxg0aFDg/ZMnTxYXX3yxePDBBwP9uOGGG4JuvNqxY4f47W9/K3JycgQA4XQ6Q3wLIURlZaU49dRTRXZ2tjjhhBPE/Pnzg7wWFhaKp556KmjboEGDxMyZMyP2j2GSAVdAYhiGlClTpuDQoUN47bXX0m2FYVIGX+ZlGIZhmAThZMowDMMwCcKXeRmGYRgmQfibKcMwDMMkCCdThmEYhkkQTqYMwzAMkyCcTBmGYRgmQTiZMgzDMEyCcDJlGIZhmAThZMowDMMwCcLJlGEYhmES5P8D4mY2hdON9m0AAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "plt.figure(figsize=(5,5))\n",
    "plt.title(\"Matplotlib demo\")\n",
    "plt.xlabel(\"x axis caption\")\n",
    "plt.ylabel(\"y axis caption\")\n",
    "for index in np.ndindex(board_info[\"shape\"][0], board_info[\"shape\"][1]):\n",
    "    plt.plot(index[0],19-index[1],\"o\",color=board_array[index])\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "614a5bdf-8f36-4d99-b4d7-cf6705c08363",
   "metadata": {},
   "source": [
    "# 数据结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "c48370ae-9b13-43b8-ab69-19ac83d15fa4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[(0, 1), (1, 1)], [(0, 5), (1, 5), (2, 5), (2, 4), (3, 5), (2, 3), (3, 6), (2, 2), (4, 6), (5, 6), (4, 7)], [(0, 11), (0, 12), (0, 13), (1, 12)], [(1, 0)], [(1, 2), (1, 3)], [(1, 6), (2, 6)], [(1, 8), (2, 8)], [(1, 9), (1, 10)], [(1, 11)], [(1, 13), (2, 13), (2, 14), (2, 12), (3, 13), (3, 12), (3, 11)], [(1, 14)], [(1, 16)], [(2, 1), (3, 1), (3, 2), (4, 2), (3, 3), (5, 2), (3, 4), (6, 2), (4, 4), (6, 3), (6, 1)], [(2, 10)], [(2, 15), (3, 15), (3, 16), (3, 14), (4, 16)], [(2, 17)], [(3, 7)], [(3, 9), (4, 9)], [(3, 17), (4, 17)], [(4, 3), (5, 3)], [(4, 14), (4, 15), (5, 15), (5, 16)], [(5, 1)], [(5, 5)], [(5, 7), (5, 8), (6, 8)], [(6, 0)], [(6, 5)], [(6, 6), (7, 6), (7, 5), (8, 5)], [(6, 7), (7, 7), (8, 7), (8, 6), (8, 8), (9, 6)], [(7, 1), (8, 1)], [(8, 2), (8, 3)], [(8, 9), (8, 10), (9, 10), (8, 11), (8, 12)], [(9, 2), (9, 3), (10, 2)], [(9, 4), (10, 4), (10, 3)], [(9, 7), (9, 8), (10, 8)], [(9, 9), (10, 9), (10, 10), (10, 11), (9, 11), (10, 12), (11, 11), (9, 12), (10, 13), (11, 12), (12, 11), (10, 14), (11, 13), (12, 12), (12, 10), (11, 14), (11, 15), (12, 14), (12, 15), (13, 14), (14, 14), (15, 14), (14, 15), (15, 15), (15, 16)], [(9, 13), (9, 14), (9, 15), (10, 15)], [(10, 5)], [(10, 7)], [(11, 3), (11, 4)], [(11, 6), (12, 6)], [(11, 7)], [(11, 9), (11, 10), (12, 9)], [(11, 16), (12, 16)], [(12, 2)], [(13, 4)], [(13, 6)], [(13, 8), (13, 9)], [(13, 15)], [(13, 17)], [(14, 1), (15, 1)], [(14, 3)], [(14, 10), (14, 11), (14, 12), (15, 12), (16, 12), (16, 13), (17, 12)], [(14, 16)], [(14, 18)], [(15, 2), (16, 2), (16, 3), (16, 1), (17, 3), (16, 4)], [(15, 5), (15, 6), (15, 7), (16, 7)], [(15, 9)], [(15, 17)], [(16, 5), (17, 5), (16, 6), (18, 5), (17, 4)], [(16, 8)], [(16, 11)], [(16, 14), (16, 15), (16, 16), (17, 15)], [(16, 18)], [(17, 2)], [(17, 7)], [(17, 8)], [(17, 9)], [(17, 14)], [(17, 17)], [(18, 1)], [(18, 3)], [(18, 13)]]\n"
     ]
    }
   ],
   "source": [
    "nns_arr = []\n",
    "pieces_arr = []\n",
    "insertionSort=[]\n",
    "a = np.zeros((board_info[\"shape\"][0],board_info[\"shape\"][1]))\n",
    "b = 1\n",
    "for r in range(0,board_info[\"shape\"][0]):\n",
    "    for c in range(0,board_info[\"shape\"][1]):\n",
    "        if np.all(board_array[r,c] != board_info[\"empty\"]) and a[r,c] == 0:\n",
    "            [d,e,f] = get_equal_neighbors(board_array,r,c)\n",
    "\n",
    "            # 用于数据结构的排序\n",
    "            if len(insertionSort)<len(d):\n",
    "                insertionSort=d.copy()\n",
    "\n",
    "            # 堆与二分搜索树\n",
    "            nns_arr.append([len(d), len(e), len(f)])\n",
    "            pieces_arr.append(d)\n",
    "            for g in d:\n",
    "                a[g[0],g[1]] = b\n",
    "            b+=1\n",
    "print(pieces_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8332957c-a7e4-4359-98a4-41993b69e267",
   "metadata": {},
   "source": [
    "## 排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e64646dd-97fd-4f7b-a5ae-16854ec4e865",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(9, 9), (10, 9), (10, 10), (10, 11), (9, 11), (10, 12), (11, 11), (9, 12), (10, 13), (11, 12), (12, 11), (10, 14), (11, 13), (12, 12), (12, 10), (11, 14), (11, 15), (12, 14), (12, 15), (13, 14), (14, 14), (15, 14), (14, 15), (15, 15), (15, 16)]\n"
     ]
    }
   ],
   "source": [
    "print(insertionSort)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73faa4c9-d3f2-42e4-abe1-1e1c1ccb5843",
   "metadata": {},
   "source": [
    "### 插入排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "2bb468df-3438-469e-80cd-5d1bad461b3f",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def insertion_sort(arr):\n",
    "    # 遍历从1到n-1\n",
    "    for i in range(1, len(arr)):\n",
    "        # 当前值为当前遍历到的位置的值\n",
    "        current_value = arr[i]\n",
    "        # 比较当前值与前面已排序的序列\n",
    "        j = i - 1\n",
    "        while j >= 0 and (arr[j][0] > current_value[0] or ( arr[j][0] == current_value[0] and arr[j][1] > current_value[1])):\n",
    "            arr[j + 1] = arr[j]\n",
    "            j -= 1\n",
    "        # 将当前值插入到正确的位置\n",
    "        arr[j + 1] = current_value\n",
    "    return arr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "b673a13b-c45f-40da-b646-9cd987cd6583",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(9, 9), (9, 11), (9, 12), (10, 9), (10, 10), (10, 11), (10, 12), (10, 13), (10, 14), (11, 11), (11, 12), (11, 13), (11, 14), (11, 15), (12, 10), (12, 11), (12, 12), (12, 14), (12, 15), (13, 14), (14, 14), (14, 15), (15, 14), (15, 15), (15, 16)]\n"
     ]
    }
   ],
   "source": [
    "insertion_sort_arr = insertion_sort(insertionSort)\n",
    "print(insertion_sort_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65051b8a-e85d-41be-870d-4c8f5fdd51a4",
   "metadata": {},
   "source": [
    "### 希尔排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "7e28d191-d73e-4bdf-b992-66745b8138e2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def shell_sort(arr):\n",
    "    n = len(arr)\n",
    "    gap = n // 2\n",
    "    while gap > 0:\n",
    "        for i in range(gap, n):\n",
    "            temp = arr[i]\n",
    "            j = i\n",
    "            while j >= gap and (arr[j - gap][0] > temp[0] or (arr[j - gap][0] == temp[0] and arr[j - gap][1] > temp[1])):\n",
    "                arr[j] = arr[j - gap]\n",
    "                j -= gap\n",
    "            arr[j] = temp\n",
    "        gap //= 2\n",
    "    return arr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "f1aca188-43e9-452a-993d-be5fa7101fba",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(9, 9), (9, 11), (9, 12), (10, 9), (10, 10), (10, 11), (10, 12), (10, 13), (10, 14), (11, 11), (11, 12), (11, 13), (11, 14), (11, 15), (12, 10), (12, 11), (12, 12), (12, 14), (12, 15), (13, 14), (14, 14), (14, 15), (15, 14), (15, 15), (15, 16)]\n"
     ]
    }
   ],
   "source": [
    "shell_sort_arr = shell_sort(insertionSort)\n",
    "print(shell_sort_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "962b0190-2afc-4f4a-bcfd-81479d16c612",
   "metadata": {},
   "source": [
    "### 归并排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "97bf3305-d9d6-4ae1-b10c-06fbd3490a70",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def merge(left_arr, right_arr):\n",
    "    result = []\n",
    "    left_index = right_index = 0\n",
    "    while left_index < len(left_arr) and right_index < len(right_arr):\n",
    "        a = left_arr[left_index]\n",
    "        b = right_arr[right_index]\n",
    "        if (a[0] < b[0] or (a[0] == b[0] and a[1] < b[1])):\n",
    "            result.append(left_arr[left_index])\n",
    "            left_index += 1\n",
    "        else:\n",
    "            result.append(right_arr[right_index])\n",
    "            right_index += 1\n",
    "    result.extend(left_arr[left_index:])\n",
    "    result.extend(right_arr[right_index:])\n",
    "    return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "559d173c-cba7-4a29-b5e1-4430fcbb0830",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def merge_sort(arr):\n",
    "    if len(arr) <= 1:\n",
    "        return arr\n",
    "    mid = len(arr) // 2\n",
    "    left_arr = arr[:mid]\n",
    "    right_arr = arr[mid:]\n",
    "    left_arr = merge_sort(left_arr)\n",
    "    right_arr = merge_sort(right_arr)\n",
    "    return merge(left_arr, right_arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "5e2f2294-70b0-4a7d-9688-15e653e7516a",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(9, 9), (9, 11), (9, 12), (10, 9), (10, 10), (10, 11), (10, 12), (10, 13), (10, 14), (11, 11), (11, 12), (11, 13), (11, 14), (11, 15), (12, 10), (12, 11), (12, 12), (12, 14), (12, 15), (13, 14), (14, 14), (14, 15), (15, 14), (15, 15), (15, 16)]\n"
     ]
    }
   ],
   "source": [
    "merge_sort_arr = merge_sort(insertionSort)\n",
    "print(merge_sort_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8751286-bc95-4a88-92a2-920532be4b8e",
   "metadata": {},
   "source": [
    "### 随机化快速排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "08926694-b787-46f0-804b-c03e24921da1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import random\n",
    "def quick_sort(arr):\n",
    "    if len(arr) <= 1:\n",
    "        return arr\n",
    "    pivot = random.choice(arr)\n",
    "    left_arr = [x for x in arr if (x[0] < pivot[0] or (x[0] == pivot[0] and x[1] < pivot[1]))]\n",
    "    mid_arr = [x for x in arr if (x[0] == pivot[0] and x[1] == pivot[1])]\n",
    "    right_arr = [x for x in arr if (x[0] > pivot[0] or (x[0] == pivot[0] and x[1] > pivot[1]))]\n",
    "    return quick_sort(left_arr) + mid_arr + quick_sort(right_arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "f6a07e73-6981-46c6-8983-7ea144bd3033",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(9, 9), (9, 11), (9, 12), (10, 9), (10, 10), (10, 11), (10, 12), (10, 13), (10, 14), (11, 11), (11, 12), (11, 13), (11, 14), (11, 15), (12, 10), (12, 11), (12, 12), (12, 14), (12, 15), (13, 14), (14, 14), (14, 15), (15, 14), (15, 15), (15, 16)]\n"
     ]
    }
   ],
   "source": [
    "sorted_arr = quick_sort(insertionSort)\n",
    "print(sorted_arr)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69105a42-465d-41d9-9fc0-167910ba237c",
   "metadata": {},
   "source": [
    "## 堆\n",
    "\n",
    ">堆是一种特殊的完全二叉树，其中的每个节点都有一个优先级，通常以节点的值来决定优先级。在最大堆中，父节点的优先级高于或等于其子节点的优先级；在最小堆中，父节点的优先级低于或等于其子节点的优先级。堆经常用于实现优先队列，因为它们能够在O(1)时间复杂度内实现插入和删除元素，同时也可以在O(logN)时间复杂度内实现查找最大（或最小）元素。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b55c22f5-4b62-4585-8e49-ccb88a809bd3",
   "metadata": {},
   "source": [
    "### 定义一个堆的类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "bac8ca26-825d-48b6-ac28-b64648de82ea",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class MaxHeap:\n",
    "    def __init__(self):\n",
    "        self.heap = []\n",
    "    def parent(self, i):\n",
    "        return (i-1)//2\n",
    "    def left_child(self, i):\n",
    "        return 2*i + 1\n",
    "    def right_child(self, i):\n",
    "        return 2*i + 2\n",
    "    def insert(self, key):\n",
    "        self.heap.append(key)\n",
    "        self.heapify_up(len(self.heap)-1)\n",
    "    def heapify_up(self, i):\n",
    "        while i > 0 and self.get_fewer_spaces(self.heap[i],self.heap[self.parent(i)]):\n",
    "            self.heap[i], self.heap[self.parent(i)] = self.heap[self.parent(i)], self.heap[i]\n",
    "            i = self.parent(i)\n",
    "    def extract_max(self):\n",
    "        max_val = self.heap[0]\n",
    "        self.heap[0] = self.heap[-1]\n",
    "        del self.heap[-1]\n",
    "        self.heapify_down(0)\n",
    "        return max_val\n",
    "    def heapify_down(self, i):\n",
    "        left = self.left_child(i)\n",
    "        right = self.right_child(i)\n",
    "        largest = i\n",
    "        if left < len(self.heap) and self.get_more_spaces(self.heap[i],self.heap[left]):\n",
    "            largest = left\n",
    "        if right < len(self.heap) and self.get_more_spaces(self.heap[largest],self.heap[right]):\n",
    "            largest = right\n",
    "        if largest != i:\n",
    "            self.heap[i], self.heap[largest] = self.heap[largest], self.heap[i]\n",
    "            self.heapify_down(largest)\n",
    "    def heapify(self, arr, n, i):\n",
    "        largest = i\n",
    "        left = 2 * i + 1\n",
    "        right = 2 * i + 2\n",
    "        if left < n and self.get_fewer_spaces(arr[largest],arr[left]):\n",
    "            largest = left\n",
    "        if right < n and self.get_fewer_spaces(arr[largest],arr[right]):\n",
    "            largest = right\n",
    "        if largest != i:\n",
    "            arr[i], arr[largest] = arr[largest], arr[i]\n",
    "            self.heapify(arr, n, largest)\n",
    "    def heapSort(self, arr):\n",
    "        n = len(arr)\n",
    "        for i in range(n//2 - 1, -1, -1):\n",
    "            self.heapify(arr, n, i)\n",
    "        for i in range(n-1, 0, -1):\n",
    "            arr[i], arr[0] = arr[0], arr[i]\n",
    "            self.heapify(arr, i, 0)\n",
    "    def get_fewer_spaces(self,h,p):\n",
    "        rt = False\n",
    "        if h[2] < p[2]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] < p[0]-p[1]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] == p[0]-p[1] and h[1] > p[1]:\n",
    "            rt = True\n",
    "        return rt\n",
    "    def get_more_spaces(self,h,p):\n",
    "        rt = False\n",
    "        if h[2] > p[2]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] > p[0]-p[1]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] == p[0]-p[1] and h[1] < p[1]:\n",
    "            rt = True\n",
    "        return rt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30e9ff45-a06d-485f-b20c-acf571753904",
   "metadata": {},
   "source": [
    "### 数据插入到堆"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "fc0df8ab-175e-47c9-834b-8f47ef472380",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[3, 6, 1], [2, 5, 1], [1, 3, 1], [1, 3, 1], [3, 5, 2], [1, 3, 1], [1, 2, 2], [1, 3, 1], [1, 3, 1], [2, 3, 2], [1, 2, 2], [1, 1, 2], [1, 2, 2], [1, 1, 3], [5, 7, 3], [3, 5, 2], [1, 2, 2], [1, 1, 3], [2, 3, 3], [1, 2, 2], [1, 2, 2], [2, 3, 3], [1, 2, 2], [2, 3, 3], [3, 3, 4], [4, 3, 2], [4, 5, 3], [1, 0, 4], [6, 8, 4], [2, 3, 3], [1, 0, 3], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 1, 2], [1, 0, 3], [4, 4, 5], [2, 3, 3], [2, 2, 4], [7, 5, 6], [2, 2, 4], [4, 3, 6], [2, 2, 4], [1, 0, 4], [1, 0, 4], [1, 1, 3], [2, 1, 5], [1, 0, 4], [1, 0, 4], [2, 2, 4], [1, 0, 4], [7, 2, 14], [11, 10, 9], [1, 0, 3], [6, 5, 6], [2, 1, 5], [4, 4, 5], [2, 2, 4], [5, 5, 5], [1, 1, 3], [1, 1, 3], [5, 5, 5], [4, 5, 4], [11, 12, 7], [1, 1, 2], [3, 4, 3], [1, 1, 3], [1, 1, 3], [1, 0, 4], [25, 20, 9], [1, 0, 3], [2, 0, 6]]\n"
     ]
    }
   ],
   "source": [
    "nns_MaxHeap = MaxHeap()\n",
    "for nns in nns_arr:\n",
    "    nns_MaxHeap.insert(nns)\n",
    "print(nns_MaxHeap.heap)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "474f54be-8983-42c1-9b48-9228afcf7d88",
   "metadata": {},
   "source": [
    "### 对堆进行排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "4513bdaf-a0cd-4b5a-abc6-f97fa9683435",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[3, 6, 1], [2, 5, 1], [1, 3, 1], [1, 3, 1], [1, 3, 1], [1, 3, 1], [1, 3, 1], [3, 5, 2], [3, 5, 2], [2, 3, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [1, 1, 2], [1, 1, 2], [1, 1, 2], [4, 3, 2], [5, 7, 3], [4, 5, 3], [3, 4, 3], [2, 3, 3], [2, 3, 3], [2, 3, 3], [2, 3, 3], [2, 3, 3], [1, 1, 3], [1, 1, 3], [1, 1, 3], [1, 1, 3], [1, 1, 3], [1, 1, 3], [1, 1, 3], [1, 0, 3], [1, 0, 3], [1, 0, 3], [1, 0, 3], [6, 8, 4], [4, 5, 4], [3, 3, 4], [2, 2, 4], [2, 2, 4], [2, 2, 4], [2, 2, 4], [2, 2, 4], [1, 0, 4], [1, 0, 4], [1, 0, 4], [1, 0, 4], [1, 0, 4], [1, 0, 4], [1, 0, 4], [5, 5, 5], [5, 5, 5], [4, 4, 5], [4, 4, 5], [2, 1, 5], [2, 1, 5], [6, 5, 6], [4, 3, 6], [7, 5, 6], [2, 0, 6], [11, 12, 7], [11, 10, 9], [25, 20, 9], [7, 2, 14]]\n"
     ]
    }
   ],
   "source": [
    "nns_MaxHeap.heapSort(nns_MaxHeap.heap)\n",
    "n = len(nns_MaxHeap.heap)\n",
    "print(nns_MaxHeap.heap)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53d93afb-eb2f-4b06-a7e1-175d90a1826e",
   "metadata": {},
   "source": [
    "## 二分搜索树（英语：Binary Search Tree）\n",
    "\n",
    ">二分搜索树是一种自平衡的二叉搜索树，其中每个节点的左子树中的所有元素的值都小于该节点的值，而右子树中的所有元素的值都大于该节点的值。二分搜索树具有高效的插入、删除和查找操作，时间复杂度为O(logN)。但是，如果树不是平衡的，最坏情况下的时间复杂度可能会退化到O(N)。二分搜索树经常用于需要快速查找和排序数据的场景。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e49fcdb-a9a6-47a1-b17d-7389cd7ffa42",
   "metadata": {},
   "source": [
    "### 定义一个二分搜索类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "b4eb9a61-632d-4bdd-8110-fc9b332da685",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class BSTNode:\n",
    "    def __init__(self, key):\n",
    "        self.left = None\n",
    "        self.right = None\n",
    "        self.val = key\n",
    "class BinarySearchTree:\n",
    "    def __init__(self):\n",
    "        self.root = None\n",
    "    def insert(self, key):\n",
    "        if self.root is None:\n",
    "            self.root = BSTNode(key)\n",
    "        else:\n",
    "            self._insert(self.root, key)\n",
    "    def _insert(self, node, key):\n",
    "        if self.get_more_spaces(key,node.val):\n",
    "            if node.left is None:\n",
    "                node.left = BSTNode(key)\n",
    "            else:\n",
    "                self._insert(node.left, key)\n",
    "        elif self.get_fewer_spaces(key,node.val):\n",
    "            if node.right is None:\n",
    "                node.right = BSTNode(key)\n",
    "            else:\n",
    "                self._insert(node.right, key)\n",
    "        else:\n",
    "            pass\n",
    "            #print(\"Value already in tree\")\n",
    "    def find(self, key):\n",
    "        if self.root is not None:\n",
    "            is_found = self._find(self.root, key)\n",
    "            if is_found:\n",
    "                return True\n",
    "            return False\n",
    "        else:\n",
    "            return None\n",
    "    def _find(self, node, key):\n",
    "        if self.get_more_spaces(key,node.val) and node.left is not None:\n",
    "            return self._find(node.left, key)\n",
    "        elif self.get_fewer_spaces(key,node.val) and node.right is not None:\n",
    "            return self._find(node.right, key)\n",
    "        if self.get_equal_spaces(key,node.val):\n",
    "            return True\n",
    "    def preorder(self):\n",
    "        if self.root is not None:\n",
    "            self._preorder(self.root)\n",
    "            print()\n",
    "    def _preorder(self, node):\n",
    "        if node is not None:\n",
    "            print(node.val,end=\",\")\n",
    "            self._preorder(node.left)\n",
    "            self._preorder(node.right)\n",
    "    def inorder(self):\n",
    "        if self.root is not None:\n",
    "            self._inorder(self.root)\n",
    "            print()\n",
    "    def _inorder(self, node):\n",
    "        if node is not None:\n",
    "            self._inorder(node.left)\n",
    "            print(node.val,end=\",\")\n",
    "            self._inorder(node.right)\n",
    "    def postorder(self):\n",
    "        if self.root is not None:\n",
    "            self._postorder(self.root)\n",
    "            print()\n",
    "    def _postorder(self, node):\n",
    "        if node is not None:\n",
    "            self._postorder(node.left)\n",
    "            self._postorder(node.right)\n",
    "            print(node.val,end=\",\")\n",
    "    def get_fewer_spaces(self,h,p):\n",
    "        rt = False\n",
    "        if h[2] < p[2]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] < p[0]-p[1]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] == p[0]-p[1] and h[1] > p[1]:\n",
    "            rt = True\n",
    "        return rt\n",
    "    def get_more_spaces(self,h,p):\n",
    "        rt = False\n",
    "        if h[2] > p[2]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] > p[0]-p[1]:\n",
    "            rt = True\n",
    "        elif h[2] == p[2] and h[0]-h[1] == p[0]-p[1] and h[1] < p[1]:\n",
    "            rt = True\n",
    "        return rt\n",
    "    def get_equal_spaces(self,h,p):\n",
    "        return h[2] == p[2] and h[0]-h[1] == p[0]-p[1] and h[1] < p[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac71f15c-c015-4e51-acef-3aec581cefac",
   "metadata": {},
   "source": [
    "### 二分搜索树节点的插入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "ec29bc86-6eed-4e3c-b6d6-159acf9fefb9",
   "metadata": {},
   "outputs": [],
   "source": [
    "bst = BinarySearchTree()\n",
    "for nns in nns_MaxHeap.heap:\n",
    "    bst.insert(nns)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6309dc11-7e8d-469a-add9-8d708db6970f",
   "metadata": {},
   "source": [
    "### 前序遍历\n",
    "\n",
    ">先访问当前节点，再依次递归访问左右子树。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "879d609b-0924-467c-9b56-23e90950c844",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3, 6, 1],[2, 5, 1],[1, 3, 1],[3, 5, 2],[2, 3, 2],[1, 2, 2],[1, 1, 2],[4, 3, 2],[5, 7, 3],[4, 5, 3],[3, 4, 3],[2, 3, 3],[1, 1, 3],[1, 0, 3],[6, 8, 4],[4, 5, 4],[3, 3, 4],[2, 2, 4],[1, 0, 4],[5, 5, 5],[4, 4, 5],[2, 1, 5],[6, 5, 6],[4, 3, 6],[7, 5, 6],[2, 0, 6],[11, 12, 7],[11, 10, 9],[25, 20, 9],[7, 2, 14],\n"
     ]
    }
   ],
   "source": [
    "bst.preorder()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b52a1d0e-6039-4ee2-ac76-443a104a787a",
   "metadata": {},
   "source": [
    "### 中序遍历\n",
    "\n",
    ">先递归访问左子树，再访问自身，再递归访问右子树。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "b388e3ee-cb91-4280-aeb9-c1363000d481",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[7, 2, 14],[25, 20, 9],[11, 10, 9],[11, 12, 7],[2, 0, 6],[7, 5, 6],[4, 3, 6],[6, 5, 6],[2, 1, 5],[4, 4, 5],[5, 5, 5],[1, 0, 4],[2, 2, 4],[3, 3, 4],[4, 5, 4],[6, 8, 4],[1, 0, 3],[1, 1, 3],[2, 3, 3],[3, 4, 3],[4, 5, 3],[5, 7, 3],[4, 3, 2],[1, 1, 2],[1, 2, 2],[2, 3, 2],[3, 5, 2],[1, 3, 1],[2, 5, 1],[3, 6, 1],\n"
     ]
    }
   ],
   "source": [
    "bst.inorder()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec8464d3-ef1e-4aff-be6e-4b9b360c2d3a",
   "metadata": {},
   "source": [
    "### 后序遍历\n",
    "\n",
    ">先递归访问左右子树，再访问自身节点。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "f65f2de0-b856-410f-9b25-36895a264e0b",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[7, 2, 14],[25, 20, 9],[11, 10, 9],[11, 12, 7],[2, 0, 6],[7, 5, 6],[4, 3, 6],[6, 5, 6],[2, 1, 5],[4, 4, 5],[5, 5, 5],[1, 0, 4],[2, 2, 4],[3, 3, 4],[4, 5, 4],[6, 8, 4],[1, 0, 3],[1, 1, 3],[2, 3, 3],[3, 4, 3],[4, 5, 3],[5, 7, 3],[4, 3, 2],[1, 1, 2],[1, 2, 2],[2, 3, 2],[3, 5, 2],[1, 3, 1],[2, 5, 1],[3, 6, 1],\n"
     ]
    }
   ],
   "source": [
    "bst.postorder()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e37fc2a5-546f-47db-9956-6326048e716b",
   "metadata": {},
   "source": [
    "## 并查集\n",
    "\n",
    ">并查集（Union-find data structure）是一种树形的数据结构，主要用于处理一些不相交集合（Disjoint Sets）的合并及查询问题。并查集常常被用于解决一些不相交集合的查询和合并操作频繁的问题，例如在图算法中的最小生成树、图的连通性等问题的解决中，它对于解决这些问题的效率非常高。\n",
    "\n",
    ">并查集主要包括两个基本操作：\n",
    ">查找操作（Find）：确定元素属于哪个集合。\n",
    ">合并操作（Union）：将两个集合合并为一个集合。\n",
    "\n",
    ">在并查集中，\"查找\"操作主要用来确定元素属于哪个集合，或者元素是否属于某个集合；\"合并\"操作则是将两个集合合并成一个新的集合。在并查集中，每个元素都有一个父节点，每个集合都有一个根节点。在\"查找\"操作中，我们可以通过父节点的链路找到根节点，从而确定元素属于哪个集合。在\"合并\"操作中，我们需要调整两个集合的父节点，将一个集合的根节点设置为另一个集合的根节点的父节点，从而将两个集合合并成一个新的集合。\n",
    "\n",
    ">并查集的主要优点是它的查找和合并操作的时间复杂度都很低：查找操作的平均时间复杂度为O(α(n))（α为阿克曼函数的反函数，增长非常慢），最坏情况是O(n)；合并操作的平均时间复杂度也为O(α(n))。所以并查集在处理大规模数据时效率非常高。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36ca89df-f64c-4dfb-b75b-86d66d5210c3",
   "metadata": {},
   "source": [
    "### 并查集类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "7a828377-1ba7-47e8-bd2b-63cd7f176ed2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class UnionFind:\n",
    "    def __init__(self, size):\n",
    "        self.parent = list(range(size))  # 初始化父节点为自身\n",
    "        self.rank = [0] * size  # 初始化树的高度为0\n",
    "    def find(self, x):\n",
    "        # 路径压缩，返回x的根节点\n",
    "        if x != self.parent[x]:\n",
    "            self.parent[x] = self.find(self.parent[x])\n",
    "        return self.parent[x]\n",
    "    def union(self, xx, yy):\n",
    "        x = xx[1] * 19 + xx[0]\n",
    "        y = yy[1] * 19 + yy[0]\n",
    "        # 将x和y所在的集合合并\n",
    "        root_x = self.find(x)\n",
    "        root_y = self.find(y)\n",
    "        if root_x != root_y:  \n",
    "            if self.rank[root_x] > self.rank[root_y]:  \n",
    "                self.parent[root_y] = root_x  \n",
    "            else:  \n",
    "                self.parent[root_x] = root_y  \n",
    "                if self.rank[root_x] == self.rank[root_y]:  \n",
    "                    self.rank[root_y] += 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "1303282d-ced8-4705-acb1-e144c403c9d6",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "go_uf = UnionFind(361)\n",
    "goed = []\n",
    "for r in range(board_info[\"shape\"][0]):\n",
    "    for c in range(board_info[\"shape\"][1]):\n",
    "        if np.all(board_array[r,c] != board_info[\"empty\"]):\n",
    "            a = get_neighbor_indices(board_array,r,c)\n",
    "            for b in a:\n",
    "                if np.all(board_array[r,c] == board_array[b[0],b[1]]) and ((b,(r,c)) not in goed):\n",
    "                    go_uf.union((r,c),b)\n",
    "                    goed.append(((r,c),b))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "7e5009d0-d7ec-46ae-a49f-b5ea4b04dff2",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "000,001,002,003,004,005,006,007,008,009,010,011,012,013,014,015,016,017,018,\n",
      "020,020,022,022,023,024,022,027,027,028,029,030,031,032,034,034,054,036,037,\n",
      "038,058,096,022,022,022,022,045,065,066,066,049,050,051,052,054,054,055,056,\n",
      "057,058,096,022,062,062,022,064,065,066,086,087,069,070,071,072,054,054,075,\n",
      "076,077,096,022,022,081,082,083,084,086,086,087,088,089,090,091,054,112,094,\n",
      "096,096,096,096,099,100,101,121,121,104,105,106,107,108,109,129,112,112,112,\n",
      "114,116,116,096,096,096,121,121,140,140,124,126,126,127,128,129,112,131,132,\n",
      "133,134,135,136,096,157,140,140,140,161,143,144,145,146,147,129,129,150,151,\n",
      "152,154,154,155,156,157,157,159,140,161,161,163,164,184,166,167,168,169,170,\n",
      "171,191,173,175,175,176,177,178,198,219,219,201,201,184,185,186,187,188,189,\n",
      "190,191,192,193,194,195,196,197,198,198,219,201,219,203,223,205,206,207,208,\n",
      "228,210,211,249,213,214,215,216,198,219,219,219,219,222,223,224,225,226,227,\n",
      "228,228,249,249,232,233,234,235,198,219,219,219,219,241,223,223,223,223,246,\n",
      "228,249,249,249,251,252,253,254,255,275,219,219,259,260,261,262,223,264,265,\n",
      "266,267,249,288,289,271,272,273,274,275,219,219,219,219,219,219,301,283,284,\n",
      "285,286,288,288,289,289,291,292,293,275,275,219,219,298,219,219,301,301,303,\n",
      "304,305,306,288,288,289,310,311,312,313,314,316,316,317,318,219,301,321,322,\n",
      "323,324,325,327,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,\n",
      "342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,\n"
     ]
    }
   ],
   "source": [
    "for r in range(19):\n",
    "    for c in range(19):\n",
    "        print(\"{:0>3}\".format(go_uf.find(r*19+c)),end=\",\")\n",
    "    print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "793338aa-4243-4b07-bc0c-6cc74d080cd2",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,\n",
      "000,001,000,001,000,000,000,000,001,000,000,000,000,000,000,001,000,000,000,\n",
      "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,001,000,000,\n",
      "000,001,001,000,000,001,000,000,001,001,000,000,000,000,000,000,000,000,000,\n",
      "000,000,000,000,000,000,000,000,000,000,001,001,000,000,000,000,000,000,000,\n",
      "000,002,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,001,000,\n",
      "000,000,001,000,000,000,000,001,000,001,000,000,001,000,000,001,000,000,000,\n",
      "000,000,000,000,000,000,000,002,000,000,000,000,000,000,000,000,000,000,000,\n",
      "000,000,001,000,000,001,000,000,000,001,000,000,000,000,000,000,000,000,000,\n",
      "000,000,000,000,001,000,000,000,000,000,001,000,000,001,000,000,000,000,000,\n",
      "000,001,000,000,000,000,000,000,001,000,000,001,000,000,000,000,000,000,000,\n",
      "000,000,000,000,000,000,000,000,000,000,002,000,000,000,001,000,000,000,000,\n",
      "001,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,\n",
      "000,000,001,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,\n",
      "000,000,000,000,000,000,000,000,000,001,000,000,000,000,000,000,000,000,000,\n",
      "000,000,000,001,001,000,000,000,000,000,000,000,000,000,000,000,001,000,000,\n",
      "000,000,000,000,000,000,000,000,000,000,000,000,001,000,000,000,000,000,000,\n",
      "000,000,000,000,001,000,000,000,000,000,000,000,000,000,000,000,000,000,000,\n",
      "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,\n"
     ]
    }
   ],
   "source": [
    "for r in range(19):\n",
    "    for c in range(19):\n",
    "        print(\"{:0>3}\".format(go_uf.rank[r*19+c]),end=\",\")\n",
    "    print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd7652c5-be54-4c4c-b2bc-66f7a22c9a9a",
   "metadata": {},
   "source": [
    "## 图"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "bb43c7a0-365a-4e04-a7c6-5289253add56",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Graph:  \n",
    "    def __init__(self, num_of_vertices):\n",
    "        self.num_of_vertices = num_of_vertices\n",
    "        self.adj_matrix = [[0] * num_of_vertices for _ in range(num_of_vertices)]\n",
    "        self.num_of_edges = 0\n",
    "    def add_edge(self, u, v):\n",
    "        self.adj_matrix[u][v] = 1\n",
    "        self.adj_matrix[v][u] = 1\n",
    "        self.num_of_edges += 2\n",
    "    def remove_node(self, u):\n",
    "        for v in range(self.num_of_vertices):\n",
    "            if self.adj_matrix[u][v] == 1:\n",
    "                self.adj_matrix[u][v] = 0\n",
    "                self.adj_matrix[v][u] = 0\n",
    "                self.num_of_edges -= 2\n",
    "            if self.adj_matrix[v][u] == 1:\n",
    "                self.adj_matrix[v][u] = 0\n",
    "                self.adj_matrix[u][v] = 0\n",
    "                self.num_of_edges -= 2\n",
    "    def remove_edge(self, u, v):\n",
    "        if self.adj_matrix[u][v] == 1:\n",
    "            self.adj_matrix[u][v] = 0\n",
    "            self.adj_matrix[v][u] = 0\n",
    "            self.num_of_edges -= 2\n",
    "        if self.adj_matrix[v][u] == 1:\n",
    "            self.adj_matrix[v][u] = 0\n",
    "            self.adj_matrix[u][v] = 0\n",
    "            self.num_of_edges -= 2\n",
    "    def get_neighbors(self, u):\n",
    "        neighbors = []\n",
    "        for v in range(self.num_of_vertices):\n",
    "            if self.adj_matrix[u][v] == 1:\n",
    "                neighbors.append(v)\n",
    "        return neighbors"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed399686-7f38-49d1-bfff-a0bcb94dba59",
   "metadata": {},
   "source": [
    ">add_edge(u, v)：添加一条从节点u到节点v的边。\n",
    "\n",
    ">remove_node(u)：删除节点u。\n",
    "\n",
    ">remove_edge(u, v)：删除从节点u到节点v的边。\n",
    "\n",
    ">get_neighbors(u)：获取节点u的邻居节点列表。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6530fae3-922c-4c4d-be81-6e35cec30288",
   "metadata": {},
   "source": [
    "### 创建棋盘图"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "821e5320-d507-4489-a4b5-80ed9cde4228",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "go_board_Graph = Graph(board_info[\"shape\"][0]*board_info[\"shape\"][1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "46d79ff9-6832-48cb-9d24-a804855c02dc",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "516\n"
     ]
    }
   ],
   "source": [
    "gbG_arr = []\n",
    "for r in range(board_info[\"shape\"][0]):\n",
    "    for c in range(board_info[\"shape\"][1]):\n",
    "        if np.all(board_array[r,c] != board_info[\"empty\"]):\n",
    "            a = get_neighbor_indices(board_array,r,c)\n",
    "            for b in a:\n",
    "                if np.all(board_array[r,c] == board_array[b[0],b[1]]) and ((b,(r,c)) not in gbG_arr):\n",
    "                    x = c * 19 + r\n",
    "                    y = b[1] * 19 + b[0]\n",
    "                    go_board_Graph.add_edge(x,y)\n",
    "                    gbG_arr.append(((r,c),b))\n",
    "print(go_board_Graph.num_of_edges)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "463af78c-6dd6-4f44-860a-baf244fc34c7",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "19 1,95 1,209 1,228 3,247 1,1 0,20 1,39 1,58 1,96 2,115 1,153 1,172 1,191 1,210 0,229 1,248 1,267 0,305 0,21 1,40 1,59 2,78 2,97 3,116 1,154 1,192 0,230 2,249 4,268 1,287 1,325 0,22 2,41 3,60 2,79 2,98 2,117 2,136 0,174 1,212 1,231 3,250 2,269 1,288 3,307 2,326 1,42 2,61 1,80 1,118 3,137 1,175 1,270 1,289 2,308 1,327 1,24 0,43 2,62 1,100 0,119 1,138 1,157 2,290 2,309 1,6 0,25 1,44 3,63 1,101 0,120 1,139 1,158 1,26 1,102 2,121 2,140 2,27 1,46 1,65 1,103 1,122 2,141 3,160 1,179 1,198 3,217 2,236 1,47 2,66 1,85 1,123 1,142 1,161 2,180 1,199 1,218 2,237 2,256 1,275 2,294 2,48 1,67 1,86 2,105 0,143 0,162 1,181 2,200 2,219 4,238 4,257 3,276 2,295 1,68 1,87 1,125 1,144 0,182 2,201 1,220 3,239 4,258 3,277 4,296 2,315 1,50 0,126 1,183 1,202 1,221 3,240 2,278 3,297 2,316 1,89 0,127 0,165 1,184 1,279 2,298 0,336 0,33 1,71 0,204 1,223 2,242 2,280 3,299 2,318 0,356 0,34 1,53 1,110 1,129 2,148 2,186 0,243 2,281 2,300 3,319 1,338 0,35 1,54 3,73 3,92 1,111 2,130 1,149 1,168 0,225 0,244 3,263 1,282 1,301 3,320 1,358 0,55 0,74 1,93 1,112 3,150 0,169 0,188 0,245 1,283 0,302 1,340 0,37 0,75 0,113 1,265 0,"
     ]
    }
   ],
   "source": [
    "for r in range(board_info[\"shape\"][0]):\n",
    "    for c in range(board_info[\"shape\"][1]):\n",
    "        if np.all(board_array[r,c] != board_info[\"empty\"]):\n",
    "            x = c * 19 + r\n",
    "            print(x,len(go_board_Graph.get_neighbors(x)),end=\",\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "923a3a5d-bc26-4cbc-84eb-f72f0188b258",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc-autonumbering": true
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
